#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<sys/resource.h>
#include<sys/time.h>

#include "../Comm/util.hpp"
#include "../Comm/Log.hpp"

namespace ns_runner{
    class Runner
    {
    public:
        Runner() {}
        ~Runner() {}
    public:
        static void  SetResourceLimit(int _cpu_limit,int _mem_limit)
        {
            //限制CPU资源
            struct rlimit cpu_limit;
            cpu_limit.rlim_cur = _cpu_limit;
            cpu_limit.rlim_max = RLIM_INFINITY;
            setrlimit(RLIMIT_CPU,&cpu_limit);

            //限制内存资源
            struct rlimit mem_limit;
            mem_limit.rlim_cur = _mem_limit * 1024;   //传入的是KB，这里函数调用是字节，需要转换
            mem_limit.rlim_max = RLIM_INFINITY;
            setrlimit(RLIMIT_AS,&mem_limit);
        }
    
        /************************************************************
         * 这里的返回值：
         * <0：代表内部出错
         * ==0：代表运行成功，没有错误
         * >0：代表运行异常，返回值就是信号编号
         * 
         * cpu_limit：限制的是程序运行时，可以占用的CPU资源上限
         * mem_limit：限制的是程序运行时，可以占用的内存资源上限
         ***********************************************************/
        static int Run(const std::string file_name,int cpu_limit, int mem_limit)
        {
            /************************************************************
             * 程序运行指挥有三种结果，
             * 程序跑完，结果对
             * 程序跑完，结果不对
             * 程序没跑完，出异常了
             * 这里我们需要考虑成功运行完即可
             * 只需要处理异常，运行的结果不管对错，放到指定文件中即可。
             * 因为运行结果是要根据测试用例来判断，结果放到文件中，后续对比即可。
             ***********************************************************/
            
            std::string execute = ns_util::PathUtil::Exe(file_name);
            // 构建程序运行时产生的临时文件
            std::string _stdin_path = ns_util::PathUtil::Stdin(file_name);
            std::string _stdout_path = ns_util::PathUtil::Stdout(file_name);
            std::string _stderr_path = ns_util::PathUtil::Stderr(file_name);

            // 打开三个文件 stdin stdout stderr
            umask(0);
            int _stdin_fd = ::open(_stdin_path.c_str(), O_CREAT | O_RDONLY, 0644);
            int _stdout_fd = ::open(_stdout_path.c_str(), O_CREAT | O_WRONLY, 0644);
            int _stderr_fd = ::open(_stderr_path.c_str(), O_CREAT | O_WRONLY, 0644);

            if (_stdin_fd < 0 | _stdout_fd < 0 | _stderr_fd < 0){
                LOG(FATAL,"运行时,标准文件打开失败");
                return -1;
            }
            pid_t id = fork();
            if (id < 0){
                LOG(FATAL,"运行时创建子进程失败");
                ::close(_stdin_fd);
                ::close(_stdout_fd);
                ::close(_stderr_fd);
                return -2;
            }
            else if (id == 0){
                // 重定向到指定的临时文件
                ::dup2(_stdin_fd, 0);
                ::dup2(_stdout_fd, 1);
                ::dup2(_stderr_fd, 2);

                SetResourceLimit(cpu_limit,mem_limit);
                ::execl(execute.c_str() /*需要执行的程序的路径*/, execute.c_str() /*怎么执行*/,nullptr);
                exit(1);
            }
            else{
                // 父进程不关心这些文件描述符，所以直接关闭
                ::close(_stdin_fd);
                ::close(_stdout_fd);
                ::close(_stderr_fd);

                int status = 0;
                ::waitpid(id,&status,0);
                LOG(INFO,"程序运行完毕,info:%d",(status & 0X7F));
                return status & 0X7F;   //返回产生异常的信号
            }
        }
    };
}